// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#pragma once

#include "mtk-sdmmc.h"

namespace {

static constexpr size_t kDescriptorChecksumSize = 16;
static constexpr uint64_t kAddressMask = 0xffffffff;

uint32_t CalculateChecksum(const void* buf) {
  const uint8_t* buf_u8 = reinterpret_cast<const uint8_t*>(buf);
  uint32_t sum = 0;
  for (size_t i = 0; i < kDescriptorChecksumSize; i++) {
    sum += buf_u8[i];
  }

  return (0xff - (sum & 0xff)) & 0xff;
}

}  // namespace

namespace sdmmc {

struct GpDmaDescriptor {
  void SetNext(uint64_t addr) {
    info = GpDmaDescriptorInfo().set_reg_value(info).set_next_addr(addr).reg_value();
    next = addr & kAddressMask;
  }

  void SetBDmaDesc(uint64_t addr) {
    info = GpDmaDescriptorInfo().set_reg_value(info).set_bdma_desc_addr(addr).reg_value();
    bdma_desc = addr & kAddressMask;
  }

  void SetChecksum() {
    auto info_reg = GpDmaDescriptorInfo().set_reg_value(info).set_checksum(0);
    info = info_reg.reg_value();
    info = info_reg.set_checksum(CalculateChecksum(this)).reg_value();
  }

  uint32_t info = 0;       // See GpDmaDescriptorInfo in mtk-sdmmc-reg.h.
  uint32_t next = 0;       // Physical address of the next GpDmaDesecriptor.
  uint32_t bdma_desc = 0;  // Physical address of the BDmaDescriptor.
  uint32_t size = 0;       // Values here and below are ignored when using one GPDMA descriptor at
  uint32_t arg = 0;        // a time.
  uint32_t blknum = 0;
  uint32_t cmd = 0;
};

struct BDmaDescriptor {
  static constexpr size_t kMaxBufferSize = 0xffff & ~kPageMask;
  static_assert(kMaxBufferSize > 0);
  static_assert(kMaxBufferSize % PAGE_SIZE == 0);

  void SetNext(uint64_t addr) {
    info = BDmaDescriptorInfo().set_reg_value(info).set_next_addr(addr).reg_value();
    next = addr & kAddressMask;
  }

  void SetBuffer(uint64_t addr) {
    info = BDmaDescriptorInfo().set_reg_value(info).set_buffer_addr(addr).reg_value();
    buffer = addr & kAddressMask;
  }

  void SetChecksum() {
    auto info_reg = BDmaDescriptorInfo().set_reg_value(info).set_checksum(0);
    info = info_reg.reg_value();
    info = info_reg.set_checksum(CalculateChecksum(this)).reg_value();
  }

  uint32_t info = 0;    // See BDmaDescriptorInfo in mtk-sdmmc-reg.h.
  uint32_t next = 0;    // Physical address of the next BDmaDescriptor.
  uint32_t buffer = 0;  // Physical address of the data buffer.
  uint32_t size = 0;    // Size of the data buffer.
};

}  // namespace sdmmc
